#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Datetime: 2020/9/27 16:47
# @Author  : CHEN Wang
# @Site    : 
# @File    : multi_factor_allocation.py
# @Software: PyCharm 

"""
脚本说明: 多因子配置方法，多因子择时，可以根据动量，拥挤度，贡献度等维度进行多因子配置
"""

import numpy as np
import pandas as pd
from quant_researcher.quant.factors.factor_analysis.factor_analyser.factor_analyse import \
    FactorAnalyseMachine
from quant_researcher.quant.factors.factor_composite.multi_factor_process import multi_factor_process
from quant_researcher.quant.factors.factor_preprocess.preprocess import ts_percentile_rank_score
from quant_researcher.quant.project_tool.db_operator import db_conn
from quant_researcher.quant.project_tool.logger.my_logger import LOG


def stock_factor_allocation(factor_list,
                            factor_num,
                            begin_date,
                            end_date,
                            universe,
                            filter_indicator,
                            filter_indicator_freq='monthly',
                            filter_indicator_weight='equal',
                            freq='monthly',
                            weight_method='等权',
                            group_num=5):
    """

    :param str factor_list: 备选因子列表，目前支持数据库中已经计算的因子,包括barra风格因子。如 'bp,ps,pcf,pe,etop,wvad'
    :param int factor_num: 每期入选因子的个数
    :param str begin_date: 策略起始日期，格式如2020-01-01
    :param str end_date: 策略结束日期，格式如2020-01-01
    :param str universe: 股票池，目前支持沪深300（HS300）， 全A（ALLA）
    :param str filter_indicator: 目前支持动量类因子择时指标 IC，IR, mom30, mom60, reversal60, reversal30, 。。。
                                        拥挤度类因子择时指标 value_spread, vol_ratio, turnover_ratio, beta_ratio,
                                        贡献度类因子择时指标 contribution；
                                也支持多个择时指标同时输入如 'IR,vol_ratio'
    :param str filter_indicator_freq: 目前支持月频（monthly），季频（quarterly）; 因子择时配置的频率必须低于调仓频率
    :param str filter_indicator_weight: 如果输入的是多个因子择时指标，则需要指定因子配权方式，默认为‘equal’
    :param str freq: 调仓频率, 目前支持日频（daily），周频（weekly），月频（monthly），季频（quarterly）
    :param str weight_method: 股票组合加权方式，目前支持等权，市值加权，价格加权
    :param int group_num: 回测分组数
    :return: 多头净值曲线，每期因子权重
    """

    con = db_conn.get_tk_factors_conn(by_sqlalchemy=True)
    factor_list = factor_list.split(',')
    data_begin_date = pd.to_datetime(begin_date) - pd.offsets.Day(365 + 30)  # 多取一年一个月

    # 获取多头组的因子收益率，并计算因子累计收益率
    factor_ret_df = pd.read_sql("select trade_date as tradedate, factor_name_en, ret from stk_factor_ret "
                                f"where trade_date>= '{data_begin_date}' "
                                f"and trade_date <= '{end_date}' "
                                f"and stock_pool = '{universe}' "
                                f"and factor_name_en in {tuple(factor_list+[''])} "
                                f"and freq ='{filter_indicator_freq}' "
                                f"and group_num = 5 ",
                                con, index_col=['tradedate', 'factor_name_en'])['ret'].unstack()

    factor_cum_ret = (factor_ret_df + 1).cumprod()
    factor_cum_ret = factor_cum_ret / factor_cum_ret.iloc[0]-1
    factor_cum_ret.index = factor_cum_ret.index.strftime("%Y-%m-%d")

    filter_indicator = filter_indicator.split(',')  # 把择时指标转换成list
    num_filter_indicator = len(filter_indicator)

    filter_df_list = []
    for indicator in filter_indicator:
        if indicator in ['IC', 'IR']:
            ic_df = pd.read_sql("select trade_date as tradedate, factor_name_en, ic from stk_factor_effectiveness "
                                f"where trade_date>= '{data_begin_date}' "
                                f"and trade_date <= '{end_date}' "
                                f"and stock_pool = '{universe}' "
                                f"and factor_name_en in {tuple(factor_list+[''])} "
                                f"and freq='{filter_indicator_freq}' ",
                                con, index_col=['tradedate', 'factor_name_en'])['ic'].unstack()
            if indicator == 'IC':
                filter_df = ic_df.rolling('365D').mean()
            elif indicator == 'IR':
                filter_df = (ic_df.rolling('365D').mean() / ic_df.rolling('365D').std())

        elif indicator[0:3] == 'mom' or indicator[0:8] == 'reversal':
            period = int(indicator.split('_')[1])
            mom_df = factor_ret_df.rolling(period, min_periods=period).apply(lambda x: (1+x).cumprod()[-1]-1)
            filter_df = mom_df.dropna(how='all')
            if indicator[0:8] == 'reversal':  # 如果计算的是反转因子，越小越好
                filter_df = filter_df * -1

        elif indicator == 'contribution':
            contribution_df = pd.read_sql("select trade_date as tradedate, factor_name_en, contribution "
                                          "from stk_factor_contribution "
                                          f"where trade_date>= '{data_begin_date}' "
                                          f"and trade_date <= '{end_date}' "
                                          f"and stock_pool = '{universe}' "
                                          f"and factor_name_en in {tuple(factor_list+[''])} ",
                                          con, index_col=['tradedate', 'factor_name_en'])['contribution'].unstack()
            filter_df = contribution_df.rolling('365D').mean()

        elif indicator in ['value_spread', 'vol_ratio', 'turnover_ratio', 'beta_ratio']:
            crowd_df = pd.read_sql("select trade_date as tradedate, factor_name_en, crowdedness "
                                   " from stk_factor_crowdedness "
                                   f"where trade_date>= '{data_begin_date}' "
                                   f"and trade_date <= '{end_date}' "
                                   f"and stock_pool = '{universe}' "
                                   f"and factor_name_en in {tuple(factor_list+[''])} "
                                   f"and metrics in {tuple(filter_indicator+[''])} ",
                                   con, index_col=['tradedate', 'factor_name_en'])['crowdedness'].unstack()
            filter_df = (100 - crowd_df.apply(ts_percentile_rank_score, way='rolling', scale=100,
                                              period='365D'))  # 拥挤度越小，相应权重越高
        else:
            LOG.warning(f'目前暂不支持该因子择时指标{indicator}，请重新输入')

        filter_df_list.append(filter_df)

    if filter_indicator_weight == 'equal':  # 每个择时因子赋权
        filter_indicator_weight = np.ones(num_filter_indicator)/num_filter_indicator
    united_filter_df = pd.DataFrame(0, columns=filter_df_list[0].columns, index=filter_df_list[0].index)
    for i in range(num_filter_indicator):  # 对因子择时指标进行排序标准化, 加权，并求和
        filter_df_list[i] = filter_df_list[i].rank(axis=1)
        filter_df_list[i] = filter_df_list[i] * filter_indicator_weight[i]
        united_filter_df = united_filter_df + filter_df_list[i]

    united_filter_df = united_filter_df.dropna(how='all')
    filter_df = united_filter_df.reset_index()

    # 下面这部分代码具体干啥用的？ 好像只是为了输出指标的原始值。
    if num_filter_indicator > 1:
        LOG.info('输入了多个因子择时指标')
        factor_indi = united_filter_df
    else:  # 如果输入的是单个择时指标
        if filter_indicator[0] == 'IC':
            factor_indi = ic_df.dropna(how='all')
        elif filter_indicator[0] == 'IR':
            factor_indi = (ic_df.rolling('365D').mean() / ic_df.rolling('365D').std()).dropna(how='all')
        elif filter_indicator[0] in ['mom_30', 'mom_60', 'reversal_60', 'reversal_90']:
            factor_indi = mom_df.dropna(how='all')
        elif filter_indicator[0] == 'contribution':
            factor_indi = contribution_df.dropna(how='all')
        else:
            factor_indi = crowd_df.dropna(how='all')
    factor_indi.index = factor_indi.index.strftime("%Y-%m-%d")
    factor_indi = factor_indi.loc[begin_date:]

    composite_factor, weights = multi_factor_process(factor_list, factor_valid_num=factor_num,
                                                     period_start=begin_date,
                                                     period_end=end_date,
                                                     method='external_weight',
                                                     filter_indicator=filter_df,
                                                     weight_indicator=filter_df,
                                                     asset_pool=universe)
    weights.index = weights.index.strftime('%Y-%m-%d')
    composite_factor.tradedate = composite_factor.tradedate.dt.strftime('%Y-%m-%d')
    FactorAnalyser = FactorAnalyseMachine(factor_name='',  # 分析因子名称
                                          begin_date=begin_date,  # 分析开始时间
                                          end_date=end_date,  # 分析结束时间
                                          external_data=True,
                                          asset_type='stock',
                                          factor_data=composite_factor,
                                          direction=[1])
    FactorAnalyser.param_setting(period=freq,  # 调仓周期
                                 universe=universe,  # 标的池仅对股票有效，基金和基金经理对应标的暂定为上证综指
                                 benchmark='000906',  # 基准代码，如果输入avg则表示所有资产池的平均值
                                 freq=1,  # 调仓频率
                                 bool_exclude_industry=True,  # 中性化是否剔除行业因子，仅对股票有效
                                 list_exclude_style=[])  # 中性化是否剔除风格因子，仅对股票有效
    FactorAnalyser.initial_data()  # 数据初始化
    ICstatsdf, long_short_df, group_performance, group_return = \
        FactorAnalyser.longshort_portfolio(
            freq=1, group_num=group_num, winsor=True, neutral=False, standard=True, weight_method=weight_method,
            abnormal_threshold=0.21, plot=False)  # 因子多空组合的净值曲线
    # group_performance.to_excel(f'group_performance_{filter_indicator}.xlsx')
    # group_return.to_excel(f'group_return{filter_indicator}.xlsx')
    long_return = ((group_return[group_num] + 1).cumprod() - 1)
    factor_weights = weights
    long_performance = group_performance.loc[f'分组{group_num}', ['信息比率', '夏普比率', '年化收益率', '年化波动率', '最大回撤', '跟踪误差']]
    long_performance = long_performance * 100
    long_performance.loc[['夏普比率', '信息比率']] /= 100
    long_performance = long_performance.round(2).astype(str)
    long_performance = long_performance + '%'
    long_performance.loc[['夏普比率', '信息比率']] = long_performance.loc[['夏普比率', '信息比率']].str.replace('%', '')
    long_performance = long_performance
    result_dict = {'long_return': long_return.to_dict(), 'long_performance': long_performance.to_dict(),
                   'factor_weights': factor_weights.to_dict(), 'factor_cum_return': factor_cum_ret.loc[long_return.index.min():].to_dict(),
                   'factor_indi_data': factor_indi.to_dict()}
    return result_dict


if __name__ == '__main__':
    begin = '2018-01-01'
    end = '2021-06-01'
    universe = 'ZZ800'
    freq = 'monthly'
    # factor_list = 'bp,ps,pcf,pe,etop,wvad'

    # factor_list1 = {'21日价格动量':'revs21', '修正动量指标': 'srmi'}
    # factor_list2 = {'60日平均换手率':'turnover60', '成交量比率': 'vr', '月度换手率对数': 'stom'}
    # factor_list3 = {'EBITDA企业价值比': 'ebitda_to_ev', '市盈率': 'pe', '市销率': 'ps'}
    # factor_list4 = {'对数市值':'log_size', '非线性市值': 'non_linear_size'}
    # factor_list5 = {'资产回报率':'roa', '市场杠杆': 'mlev'}
    # factor_list6 = {'营业利润增长率':'oper_profit_growth', '5年收益增长率': 'egro'}

    # 反向因子
    # factor_list1 = {'21日价格动量':'revs21'}
    # factor_list2 = {'60日平均换手率':'turnover60','月度换手率对数': 'stom'}
    # factor_list3 = {'252日残差收益波动率':'hsigmacne5'}
    # 正向因子
    # factor_list4 = {'EBITDA企业价值比': 'ebitda_to_ev', '收入市值比': 'rev_to_mkv'}
    # factor_list5 = {'资产回报率':'roa', '权益回报率': 'roe', '利息保障倍数': 'interest_cover'}
    # factor_list6 = {'基本每股收益':'basic_eps', '每股经营活动产生的现金流量净额': 'operating_cashflow_ps'}
    # factor_list7 = {'营业利润增长率':'oper_profit_growth', '营业收入增长率': 'oper_revenue_growth'}

    factor_list1 = {'市净率':'pb', '市现率': 'pcf', '市销率':'ps'}  # 估值因子
    factor_list2 = {'对数市值': 'log_size'}  # 市值因子
    factor_list3 = {'资产回报率':'roa', '权益回报率': 'roe', '利息保障倍数': 'interest_cover'}  # 财务质量因子
    factor_list4 = {'基本每股收益':'basic_eps', '每股企业自由现金流量': 'fcff_ps'}  # 每股指标因子
    factor_list5 = {'5年收益增长率':'egro','总资产增长率': 'total_asset_growth'}   # 成长因子
    factor_list6 = {'12个月累计收益范围的对数':'cmra12', '126日信息比率':'informationratio126'}  # 收益风险因子
    factor_list7 = {'振动升降指标':'swingindex'}  # 技术指标因子

    # factor_list1 = {'EBITDA企业价值比': 'ebitda_to_ev', '收益市值比':'etop',
    #                 '市净率':'pb', '市现率': 'pcf'}  # 估值因子
    # factor_list2 = {'对数市值': 'log_size', '流通市值':'neg_mkv'}  # 市值因子
    # factor_list3 = {'资产回报率':'roa', '权益回报率': 'roe', '利息保障倍数': 'interest_cover',
    #                 '总资产周转率':'total_assets_turunover_rate'}  # 财务质量因子
    # factor_list4 = {'基本每股收益':'basic_eps', '每股企业自由现金流量': 'fcff_ps', '每股经营活动产生的现金流量净额': 'operating_cashflow_ps'}  # 每股指标因子
    # factor_list5 = {'总资产增长率': 'total_asset_growth'}   # 成长因子
    # factor_list6 = {'126日信息比率':'informationratio126'}  # 收益风险因子
    # factor_list7 = {'504日相对强势': 'rstr504'}  # 动量因子

    factor_list = [factor_list1, factor_list2, factor_list3, factor_list4, factor_list5,
                   factor_list6, factor_list7]
    all_factor = []
    for i in factor_list:
        all_factor = all_factor + list(i.values())

    factor_list = ','.join(all_factor)

    # filter_indicator = 'mom_30,reversal_90'
    for i in [20]:
        for j in [120]:
            filter_indicator = f'mom_{i},reversal_{j}'
            test = result = stock_factor_allocation(factor_list=factor_list,
                                                    factor_num=5,
                                                    begin_date=begin,
                                                    end_date=end,
                                                    universe=universe,
                                                    filter_indicator=filter_indicator,
                                                    filter_indicator_freq='monthly',
                                                    filter_indicator_weight='equal',
                                                    freq=freq,
                                                    weight_method='等权',
                                                    group_num=10)